admin / Synapse-Sonar
publicAttack Surface Simulation
Synapse-Sonar / synapse-sonar / app / api / assets / [id] / route.ts
1581 B · main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | import { NextRequest, NextResponse } from "next/server"; import { z } from "zod"; import { requireTenantContext, HttpError } from "@/lib/auth"; import { assertCan } from "@/lib/rbac"; import { prisma } from "@/lib/prisma"; export const runtime = "nodejs"; const PatchSchema = z.object({ offline: z.boolean(), }); /** * PATCH /api/assets/:id * * Operator controls for a single asset. Currently: manually toggle the node's * offline state. Tenant-scoped and gated behind asset:write (Analyst+). */ export async function PATCH( req: NextRequest, { params }: { params: { id: string } } ) { try { const ctx = await requireTenantContext(); assertCan(ctx.role, "asset:write"); const { offline } = PatchSchema.parse(await req.json()); // updateMany with the tenant guard guarantees no cross-tenant write. const result = await prisma.asset.updateMany({ where: { id: params.id, tenantId: ctx.tenantId }, data: { manualOffline: offline }, }); if (result.count === 0) { return NextResponse.json({ error: "Asset not found" }, { status: 404 }); } return NextResponse.json({ ok: true, offline }); } catch (err) { if (err instanceof HttpError) { return NextResponse.json({ error: err.message }, { status: err.status }); } if (err instanceof z.ZodError) { return NextResponse.json({ error: "Invalid input", details: err.issues }, { status: 422 }); } const status = (err as { status?: number }).status ?? 500; return NextResponse.json({ error: (err as Error).message }, { status }); } } |